package engine.utils
{
	
	import fl.motion.BezierSegment;
	
	import flash.display.Graphics;
	import flash.geom.Point;
	import flash.geom.Rectangle;

	public class GraphicUtils
	{
		public static function drawArrow(graphics:Graphics, arrowWidth:Number = 20, arrowHeight:Number = 20, rectWidth:Number = 10, rectHeight:Number = 10):void
		{
			graphics.moveTo(arrowHeight, 0);
			graphics.lineTo(0, -arrowWidth / 2);
			graphics.lineTo(0, -rectWidth / 2);
			graphics.lineTo(-rectHeight, -rectWidth / 2);
			graphics.lineTo(-rectHeight, rectWidth / 2);
			graphics.lineTo(0, rectWidth / 2);
			graphics.lineTo(0, arrowWidth / 2);
			graphics.lineTo(arrowHeight, 0);
		}
		
		public static function drawGear(graphics:Graphics, outerRadius:Number, innerRadius:Number, width:Number, numPoints:int, x:Number = 0, y:Number = 0, radians:Number = 0):void
		{
			const offsetRadians:Number = Math.PI * 2 / numPoints;
			const halfWidth:Number = width / 2;
			const radDiff:Number = outerRadius - innerRadius;
			
			var offsetX:Number = x + outerRadius * Math.cos(radians) - halfWidth * Math.sin(radians) - radDiff * Math.cos(radians);
			var offsetY:Number = y + outerRadius * Math.sin(radians) - halfWidth * Math.cos(radians) - radDiff * Math.sin(radians);
			graphics.moveTo(offsetX, offsetY);
			
			for(var i:int = 0; i <= numPoints; i++)
			{
				offsetX = x + outerRadius * Math.cos(radians) - halfWidth * Math.sin(radians);
				offsetY = y + outerRadius * Math.sin(radians) - halfWidth * Math.cos(radians);
				graphics.moveTo(offsetX, offsetY);
				
				
				offsetX = x + outerRadius * Math.cos(radians) + halfWidth * Math.sin(radians);
				offsetY = y + outerRadius * Math.sin(radians) + halfWidth * Math.cos(radians);
				graphics.lineTo(offsetX, offsetY);
				
				offsetX = x + outerRadius * Math.cos(radians) + halfWidth * Math.sin(radians);
				offsetY = y + outerRadius * Math.sin(radians) + halfWidth * Math.cos(radians);
				graphics.lineTo(offsetX, offsetY);
				
				offsetX = x + outerRadius * Math.cos(radians) + halfWidth * Math.sin(radians) - radDiff * Math.cos(radians);
				offsetY = y + outerRadius * Math.sin(radians) + halfWidth * Math.cos(radians) - radDiff * Math.sin(radians);
				graphics.lineTo(offsetX, offsetY);
				
				radians += offsetRadians;
			}
		}
		
		public static function gear(target:Graphics, x:Number, y:Number, 
									sides:Number, innerRadius:Number, outerRadius:Number, 
									angle:Number, holeSides:Number, holeRadius:Number ):void 
		{
			if (sides>2) {
				// init vars
				var step, qtrStep, start, n, dx, dy;
				// calculate length of sides
				step = (Math.PI*2)/sides;
				qtrStep = step/4;
				// calculate starting angle in radians
				start = (angle/180)*Math.PI;
				target.moveTo(x+(Math.cos(start)*outerRadius), y-(Math.sin(start)*outerRadius));
				// draw lines
				for (n=1; n<=sides; n++) {
					dx = x+Math.cos(start+(step*n)-(qtrStep*3))*innerRadius;
					dy = y-Math.sin(start+(step*n)-(qtrStep*3))*innerRadius;
					target.lineTo(dx, dy);
					dx = x+Math.cos(start+(step*n)-(qtrStep*2))*innerRadius;
					dy = y-Math.sin(start+(step*n)-(qtrStep*2))*innerRadius;
					target.lineTo(dx, dy);
					dx = x+Math.cos(start+(step*n)-qtrStep)*outerRadius;
					dy = y-Math.sin(start+(step*n)-qtrStep)*outerRadius;
					target.lineTo(dx, dy);
					dx = x+Math.cos(start+(step*n))*outerRadius;
					dy = y-Math.sin(start+(step*n))*outerRadius;
					target.lineTo(dx, dy);
				}
				// This is complete overkill... but I had it done already. :)
				if (holeSides>2) {
					
					step = (Math.PI*2)/holeSides;
					target.moveTo(x+(Math.cos(start)*holeRadius), y-(Math.sin(start)*holeRadius));
					for (n=1; n<=holeSides; n++) {
						dx = x+Math.cos(start+(step*n))*holeRadius;
						dy = y-Math.sin(start+(step*n))*holeRadius;
						target.lineTo(dx, dy);
					}
				}
			}
		}
		
		public static function drawEllipse(graphics:Graphics, radiusX:Number, radiusY:Number, numPoints:int, x:Number = 0, y:Number = 0):void
		{
			var radians:Number;
			
			var offsetX:Number = radiusX * Math.cos(0) + x;
			var offsetY:Number = radiusY * Math.sin(0) + y;
			graphics.moveTo(offsetX, offsetY);
			
			for(var i:int = 0; i <= numPoints; i++)
			{
				radians = (i / numPoints) * MathUtils.PI2;
				
				offsetX = radiusX * Math.cos(radians) + x;
				offsetY = radiusY * Math.sin(radians) + y;
				graphics.lineTo(offsetX, offsetY);
			}
		}
		
		public static function drawLines(graphics:Graphics, points:Array):void
		{
			if(!points) return;
			
			graphics.moveTo(points[0].x, points[0].y);
			
			for(var i:int = 1; i < points.length; i++)
				graphics.lineTo(points[i].x, points[i].y);
		}
		
		/**
		 * Draws a circle outline given a center Point, radius, and size.
		 * 
		 * @param Graphics: The Graphics of a DisplayObject.
		 * @param Center: A center Point.
		 * @param Radius: A radius to use for dimensions.
		 * @param Size: A size to use for the outline.
		 */
		public static function drawCircleOutline(graphics:Graphics, center:Point, radius:Number, size:Number):void
		{
			graphics.drawCircle(center.x, center.y, radius);
			graphics.drawCircle(center.x, center.y, radius - size);
		}
		
		/**
		 * Draws an ellipse outline given the dimensions of a Rectangle and a size.
		 * 
		 * @param Graphics: The Graphics of a DisplayObject.
		 * @param Rect: A Rectangle to use for dimensions.
		 * @param Size: A size to use for the outline.
		 */
		public static function drawEllipseOutline(graphics:Graphics, rect:Rectangle, size:Number):void
		{
			graphics.drawEllipse(rect.x, rect.y, rect.width, rect.height);
			graphics.drawEllipse(rect.x + size, rect.y + size, rect.x - size * 2, rect.y - size * 2);
		}
		
		/**
		 * Draws a rectangle outline given the dimensions of a Rectangle and a size.
		 * 
		 * @param Graphics: The Graphics of a DisplayObject.
		 * @param Rect: A Rectangle to use for dimensions.
		 * @param Size: A size to use for the outline.
		 */
		public static function drawRectangleOutline(graphics:Graphics, rect:Rectangle, size:Number):void
		{
			graphics.drawRect(rect.x, rect.y, rect.width, rect.height);
			graphics.drawRect(rect.x + size, rect.y + size, rect.width - size * 2, rect.height - size * 2);
		}
		
		public static function drawDroplet(graphics:Graphics, radius:Number):void
		{
			graphics.moveTo(0, radius);
			
			//Front "C" Curve
			graphics.curveTo(radius, 				radius, 	radius, 			0);
			graphics.curveTo(radius, 				-radius, 	0, 					-radius);
			
			//Tope "S" Curve
			graphics.curveTo(-radius * 3 * 0.5, 	-radius, 	-radius * 3, 		-radius * 0.5);
			graphics.curveTo(-radius * 3 * 1.5, 	0, 			-radius * 3 * 2, 	0);
			
			//Bottom "S" Curve
			graphics.curveTo(-radius * 3 * 1.5,		0,			-radius * 3,		radius * 0.5);
			graphics.curveTo(-radius * 3 * 0.5,		radius,		0,					radius);	
		}
		
		public static function drawPolygon(graphics:Graphics, radius:Number, numPoints:int, x:Number = 0, y:Number = 0, radians:Number = 0):void
		{
			const offsetRadians:Number = Math.PI * 2 / numPoints;
			
			var offsetX:Number = radius * Math.cos(radians) + x;
			var offsetY:Number = radius * Math.sin(radians) + y;
			graphics.moveTo(offsetX, offsetY);
			
			for(var i:int = 0; i <= numPoints; i++)
			{
				radians += offsetRadians;
				offsetX = radius * Math.cos(radians) + x;
				offsetY = radius * Math.sin(radians) + y;
				graphics.lineTo(offsetX, offsetY);
			}	
		}
		
		public static function drawStar(graphics:Graphics, outerRadius:Number, innerRadius:Number, numPoints:int, x:Number = 0, y:Number = 0, radians:Number = 0):void
		{
			const offsetRadians:Number = Math.PI / numPoints;
			
			var offsetX:Number = outerRadius * Math.cos(radians) + x;
			var offsetY:Number = outerRadius * Math.sin(radians) + y;
			graphics.moveTo(offsetX, offsetY);
			
			for(var i:int = 0; i < numPoints; i++)
			{
				radians += offsetRadians;
				offsetX = x + innerRadius * Math.cos(radians);
				offsetY = y + innerRadius * Math.sin(radians);
				graphics.lineTo(offsetX, offsetY);
				
				radians += offsetRadians;
				offsetX = x + outerRadius * Math.cos(radians);
				offsetY = y + outerRadius * Math.sin(radians);
				graphics.lineTo(offsetX, offsetY);
			}
		}
		
		public static function drawCurvedStar(graphics:Graphics, outerRadius:Number, innerRadius:Number, numPoints:int, concave:Boolean = true, x:Number = 0, y:Number = 0, radians:Number = 0):void
		{
			const offsetRadians:Number = Math.PI / numPoints;
			
			var controlX:Number;
			var controlY:Number;
			var anchorX:Number;
			var anchorY:Number;
			
			if(concave)
			{
				var temp:Number = innerRadius;
				innerRadius 	= outerRadius;
				outerRadius 	= temp;
			}
			anchorX = x + innerRadius * Math.cos(radians);
			anchorY = y + innerRadius * Math.sin(radians);
			graphics.moveTo(anchorX, anchorY);
			
			for(var i:int = 0; i < numPoints; i++)
			{
				radians += offsetRadians;
				controlX = x + outerRadius * Math.cos(radians);
				controlY = y + outerRadius * Math.sin(radians);
				
				radians += offsetRadians;
				anchorX = x + innerRadius * Math.cos(radians);
				anchorY = y + innerRadius * Math.sin(radians);
				
				graphics.curveTo(controlX, controlY, anchorX, anchorY);
			}
		}
		
		/**
		 * Draws a curve through an Array of points.
		 * 
		 * @param Graphics: The Graphics of a DisplayObject.
		 * @param Points: An Array of Points.
		 * @param Z: Something...
		 * @param AngleFactor: Something...
		 * @see http://cartogrammar.com/source/CubicBezier_May08.as
		 */
		public static function drawCurves(graphics:Graphics, points:Array, z:Number = 0.5, angleFactor:Number = 0.75):void
		{
			var p:Array = points.slice();
			
			if (z <= 0) z = 0.5;
			else if (z > 1) z = 1;
			
			if (angleFactor < 0) angleFactor = 0;
			else if (angleFactor > 1) angleFactor = 1;
			
			if(p.length < 2) return;
			else if (p.length == 2)
			{	
				graphics.moveTo(p[0].x,p[0].y);
				graphics.lineTo(p[1].x,p[1].y);
				return;
			}
			
			var first:int = 1;
			var last:int = p.length - 1;
			
			if (p[0].x == p[p.length-1].x && p[0].y == p[p.length-1].y)
			{
				first = 0;
				last = p.length;
			}
			
			var controlPts:Array = new Array();
			
			for (var i:int = first; i < last; i++)
			{
				var point1:Point = (i - 1 < 0) ? p[p.length - 2] : p[i - 1];
				var point2:Point = p[i];
				var point3:Point = (i + 1 == p.length) ? p[1] : p[i + 1];
				
				var dist1:Number 			= Point.distance(point1, point2);
				if (dist1 < 0.001) dist1 	= 0.001;
				
				var dist2:Number 			= Point.distance(point2, point3);
				if (dist2 < 0.001) dist2 	= 0.001;
				
				var dist3:Number 			= Point.distance(point1,point3);
				if (dist3 < 0.001) dist3 	= 0.001;
				
				var radians:Number = Math.acos((dist2 * dist2 + dist1 * dist1 - dist3 * dist3) / (2 * dist2 * dist1));
				
				var aPt:Point = new Point(point1.x - point2.x, point1.y - point2.y);
				var bPt:Point = new Point(point2.x, point2.y);
				var cPt:Point = new Point(point3.x - point2.x, point3.y - point2.y);
				
				if (dist1 > dist2) aPt.normalize(dist2);
				else if (dist2 > dist1) cPt.normalize(dist1);
				
				aPt.offset(point2.x, point2.y);
				cPt.offset(point2.x, point2.y);
				
				var rx:Number = (bPt.x - aPt.x) + (bPt.x - cPt.x);
				var ry:Number = (bPt.y - aPt.y) + (bPt.y - cPt.y);
				var theta:Number = Math.atan(ry / rx);
				
				var controlDist:Number 			= Math.min(dist1, dist2) * z;
				var controlScaleFactor:Number 	= radians / Math.PI;
				controlDist 					*= ((1 - angleFactor) + angleFactor * controlScaleFactor);
				var controlAngle:Number 		= theta + MathUtils.PI2;
				var controlPoint2:Point 		= Point.polar(controlDist, controlAngle);
				var controlPoint1:Point 		= Point.polar(controlDist, controlAngle + Math.PI);
				
				controlPoint1.offset(point2.x, point2.y);
				controlPoint2.offset(point2.x, point2.y);
				
				if (Point.distance(controlPoint2, point3) > Point.distance(controlPoint1, point3))
					controlPts[i] = new Array(controlPoint2, controlPoint1);
				else controlPts[i] = new Array(controlPoint1, controlPoint2);
				
				//Uncomment to draw lines showing where the control points are.
				/*
				graphics.moveTo(point2.x, point2.y);
				graphics.lineTo(controlPoint2.x, controlPoint2.y);
				graphics.moveTo(point2.x, point2.y);
				graphics.lineTo(controlPoint1.x, controlPoint1.y);
				*/				
			}
			
			graphics.moveTo(p[0].x, p[0].y);
			
			if (first == 1) graphics.curveTo(controlPts[1][0].x, controlPts[1][0].y, p[1].x, p[1].y);
			
			for(i = first; i < last - 1; i++)
			{
				var bezier:BezierSegment = new BezierSegment(p[i], controlPts[i][1], controlPts[i+1][0], p[i+1]);
				
				// Construct the curve out of 100 segments (adjust number for less/more detail)
				for(var t:Number = 0.01; t < 1.01; t +=.01)
				{
					var value:Point = bezier.getValue(t);
					graphics.lineTo(value.x, value.y);
				}
			}
			
			if (last == p.length - 1) graphics.curveTo(controlPts[i][1].x, controlPts[i][1].y, p[i+1].x, p[i+1].y);
		}
	}
}